package test.m2.serviceproxy;

import org.fornax.cartridges.sculptor.framework.errorhandling.ServiceContext;

import java.net.URL;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import java.util.Random;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;

import test.m2.domain.X;
import test.m2.exception.XNotFoundException;
import test.m2.serviceapi.B;

/**
 * Generated proxy class that can be used by clients to invoke B.
 */
public class BProxy implements B {
    private static final String JNP = "jnp://";
    private B service;
    private String jndiName;
    private String earName;
    private boolean remote;
    private String providerUrl;

    // you can define default partitionName in sculptor-generator.properties deployment.jnpPartitionName
    private String partitionName;
    private Random random = new Random(0);

    public BProxy() {
    }

    /**
     * Dependency injection of the EJB to invoke. If service is not injected
     * lookup based on jndiName will be used.
     */
    public void setService(B service) {
        this.service = service;
    }

    /**
     * Dependency injection of JNDI name of EJB to invoke.
     * Only necessary if default naming isn't enough.
     */
    public void setJndiName(String jndiName) {
        this.jndiName = jndiName;
    }

    /**
     * Dependency injection of earName that is used for default
     * jndiName. Only necessary if default naming isn't enough.
     */
    public void setEarName(String earName) {
        this.earName = earName;
    }

    protected B getService() {
        if (service != null) {
            return service;
        }
        try {
            if (jndiName == null) {
                jndiName = defaultJndiName();
            }
            InitialContext ctx = createInitialContext();
            B ejb = (test.m2.serviceapi.B) ctx.lookup(jndiName);

            // don't cache the remote proxy instance, since it might be stale and the server 
            // of the proxy might have crashed.
            if (!useRemoteLookup()) {
                setService(ejb);
            }

            return ejb;
        } catch (NamingException e) {
            throw new RuntimeException("Failed to lookup " + jndiName +
                ", due to " + e.getMessage(), e);
        }
    }

    private String defaultJndiName() {
        if (earName() == null) {

            // this requires definition of ejb-local-ref (in web.xml)
            return "java:comp/env/ejb/b/" + localOrRemote();
        } else {
            return earName + "/b/" + localOrRemote();
        }
    }

    private String earName() {
        if (earName == null) {
            earName = defaultEarName();
        }
        return earName;
    }

    private String defaultEarName() {

        // you can define deployment.earname in sculptor-generator.properties if you need to define 
        // the name of the ear instead of resolving it from the resourceName like this
        String resourceName =
            "/" + getClass().getName().replace('.', '/') + ".class";
        URL resource = getClass().getResource(resourceName);
        String fullPath = resource.toExternalForm();
        String[] parts = fullPath.split("/");
        for (int i = 0; i < parts.length; i++) {
            if (parts[i].endsWith(".ear")) {

                // remove .ear
                String earBaseName =
                    parts[i].substring(0, parts[i].length() - 4);
                return earBaseName;
            }
        }

        // ear not found
        return null;

    }

    public boolean isRemote() {
        return remote;
    }

    /**
     * When setting this to true remote ejb interface is used. By default local
     * ejb interface is used.
     */
    public void setRemote(boolean remote) {
        this.remote = remote;
    }

    private String localOrRemote() {
        return (remote ? "remote" : "local");
    }

    public String getProviderUrl() {
        return providerUrl;
    }

    /**
     * InitialContext Context.PROVIDER_URL, for example
     * jnp://host1:1099,host2:1099
     */
    public void setProviderUrl(String providerUrl) {
        this.providerUrl = providerUrl;
    }

    protected boolean useRemoteLookup() {
        return (remote && (providerUrl != null || partitionName != null));
    }

    public String getPartitionName() {
        return partitionName;
    }

    /**
     * InitialContext JBoss cluster partition parameter, jnp.partitionName. Used
     * for remote lookup.
     */
    public void setPartitionName(String partitionName) {
        this.partitionName = partitionName;
    }

    protected InitialContext createInitialContext() throws NamingException {
        if (!useRemoteLookup()) {
            return new InitialContext();
        }
        Properties p = new Properties();

        // doc of properties: http://community.jboss.org/wiki/NamingContextFactory
        p.put(Context.INITIAL_CONTEXT_FACTORY,
            "org.jnp.interfaces.NamingContextFactory");
        p.put(Context.URL_PKG_PREFIXES, "jboss.naming:org.jnp.interfaces");
        if (providerUrl != null) {
            p.put(Context.PROVIDER_URL, sortProviderUrl());
        }
        if (partitionName != null) {
            // JBoss cluster partition parameter
            p.put("jnp.partitionName", partitionName);
        }

        return new InitialContext(p);
    }

    /**
     * Sort with preference of current "localhost", i.e. prefer local calls over
     * remote. "localhost" is determined from System property
     * "jboss.bind.address".
     */
    private String sortProviderUrl() {
        if (providerUrl == null) {
            throw new IllegalArgumentException("providerUrl must be defined");
        }
        String str;
        if (providerUrl.startsWith(JNP)) {
            str = providerUrl.substring(JNP.length());
        } else {
            str = providerUrl;
        }
        String[] split = str.split(",");
        if (split.length <= 1) {
            return providerUrl;
        }
        String bindAddress =
            System.getProperty("jboss.bind.address", "localhost");
        String primary = null;
        List<String> rest = new ArrayList<String>();
        for (String each : split) {
            if (primary == null && each.startsWith(bindAddress + ":")) {
                primary = each;
            } else {
                rest.add(each);
            }
        }
        StringBuilder result = new StringBuilder();
        if (providerUrl.startsWith(JNP)) {
            result.append(JNP);
        }
        if (primary != null) {
            result.append(primary);
            if (!rest.isEmpty()) {
                result.append(",");
            }
        }
        if (rest.size() > 1) {
            Collections.shuffle(rest, random);
        }
        if (rest.size() == 1) {
            result.append(rest.get(0));
        } else {
            for (Iterator<String> iter = rest.iterator(); iter.hasNext();) {
                String each = iter.next();
                result.append(each);
                if (iter.hasNext()) {
                    result.append(",");
                }
            }
        }

        return result.toString();
    }

    public String b(ServiceContext ctx) {
        return getService().b(ctx);
    }

    public X x(ServiceContext ctx, String name) throws XNotFoundException {
        return getService().x(ctx, name);
    }

    public X save(ServiceContext ctx, X entity) {
        return getService().save(ctx, entity);
    }
}
